package sm.upgrade.api.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate3.HibernateTemplate;

import sm.hibernate.DAMUtil;
import sm.upgrade.api.BatchDataUpgradeService;
import sm.upgrade.api.DataService;
import sm.upgrade.api.DataUpgradeInterceptor;
import sm.upgrade.util.KeyValueChangePolicy;
import sm.upgrade.util.UpgradeResult;
import sm.util.SMMap;
import sm.util.Util;

import com.google.gson.Gson;

public class BatchDataUpgradeServiceImpl implements BatchDataUpgradeService {

	private static Logger logger = LoggerFactory
			.getLogger(BatchDataUpgradeService.class);

	private HibernateTemplate template;

	public void setTemplate(HibernateTemplate template) {
		this.template = template;
	}

	/**
	 * compare upgrade data with customer data, get all conflict paths
	 * 
	 * @param fieldPaths
	 * @param upgradeData
	 * @param customerData
	 * @return
	 */
	private List<String> compare(List<String> fieldPaths, SMMap upgradeData,
			SMMap customerData) {
		List<String> conflictPaths = new ArrayList<String>();
		for (String fieldPath : fieldPaths) {
			Object upgradeValue = upgradeData.get(fieldPath);
			Object customerValue = customerData.get(fieldPath);
			if (!Util.equals(upgradeValue, customerValue)) {
				conflictPaths.add(fieldPath);
			}
		}
		return conflictPaths;
	}

	/**
	 * compare upgrade data with customer data, get all conflict paths, for
	 * non-pk data
	 * 
	 * @param fieldPaths
	 * @param upgradeDatas
	 * @param customerDatas
	 * @return
	 */
	private List<String> compare(List<String> fieldPaths,
			List<SMMap> upgradeDatas, List<SMMap> customerDatas) {
		List<String> conflictPaths = new ArrayList<String>();
		for (String fieldPath : fieldPaths) {
			List<Object> upgradeValues = getValues(upgradeDatas, fieldPath);
			List<Object> customerValues = getValues(customerDatas, fieldPath);
			if (!Util.equals(upgradeValues, customerValues)) {
				conflictPaths.add(fieldPath);
			}
		}
		return conflictPaths;
	}

	private static void logConflict(String fieldPath, Object upgradeValue,
			Object customerValue) {
		logConflict(fieldPath, upgradeValue, customerValue, null, false);
	}

	private static void logConflict(String fieldPath, Object upgradeValue,
			Object customerValue, Object baseValue) {
		logConflict(fieldPath, upgradeValue, customerValue, baseValue, true);
	}

	private static void logConflict(String fieldPath, Object upgradeValue,
			Object customerValue, Object baseValue, boolean compareWithBase) {
		if (logger.isDebugEnabled()) {
			logger.debug("field: " + fieldPath);
			Gson gson = new Gson();
			logger.debug("upgrade value: " + gson.toJson(upgradeValue));
			logger.debug("customer value: " + gson.toJson(customerValue));
			if (compareWithBase) {
				logger.debug("base value: " + gson.toJson(baseValue));
			}
		}
	}

	/**
	 * compare base data with customer data and upgrade data, for non-pk data
	 * 
	 * @param fieldPaths
	 * @param upgradeDatas
	 * @param customerDatas
	 * @param baseDatas
	 * @return
	 */
	private UpgradeResult compare(List<String> fieldPaths,
			List<SMMap> upgradeDatas, List<SMMap> customerDatas,
			List<SMMap> baseDatas) {
		UpgradeResult result = UpgradeResult.Already_Current;
		String preFieldPath = null;
		List<Object> preUpgradeValues = null;
		List<Object> preCustomerValues = null;
		List<Object> preBaseValues = null;
		for (String fieldPath : fieldPaths) {
			List<Object> upgradeValues = getValues(upgradeDatas, fieldPath);
			List<Object> customerValues = getValues(customerDatas, fieldPath);
			List<Object> baseValues = getValues(baseDatas, fieldPath);
			// compare with base data with customer data
			if (!Util.equals(baseValues, customerValues)) {
				// compare the base data with upgrade data
				if (!Util.equals(baseValues, upgradeValues)) {
					result = UpgradeResult.Renamed;
					if (logger.isDebugEnabled()) {
						logConflict(fieldPath, upgradeValues, customerValues,
								baseValues);
					}
					break;
				} else {
					if (result == UpgradeResult.Upgraded) {
						result = UpgradeResult.Renamed;
						if (logger.isDebugEnabled()) {
							logConflict(preFieldPath, preUpgradeValues,
									preCustomerValues, preBaseValues);
							logConflict(fieldPath, upgradeValues,
									customerValues, baseValues);
						}
						break;
					} else {
						result = UpgradeResult.Kept_Customer;
						preFieldPath = fieldPath;
						preUpgradeValues = upgradeValues;
						preCustomerValues = customerValues;
						preBaseValues = baseValues;
					}
				}
			} else {
				if (result == UpgradeResult.Kept_Customer) {
					result = UpgradeResult.Renamed;
					if (logger.isDebugEnabled()) {
						logConflict(preFieldPath, preUpgradeValues,
								preCustomerValues, preBaseValues);
						logConflict(fieldPath, upgradeValues, customerValues,
								baseValues);
					}
					break;
				} else {
					result = UpgradeResult.Upgraded;
					preFieldPath = fieldPath;
					preUpgradeValues = upgradeValues;
					preCustomerValues = customerValues;
					preBaseValues = baseValues;
				}
			}
		}

		return result;
	}

	/**
	 * compare base data with customer data and upgrade data
	 * 
	 * @param fieldPaths
	 * @param upgradeData
	 * @param customerData
	 * @param baseData
	 * @return
	 */
	private static UpgradeResult compare(List<String> fieldPaths,
			SMMap upgradeData, SMMap customerData, SMMap baseData) {
		UpgradeResult result = UpgradeResult.Renamed;
		String preFieldPath = null;
		Object preUpgradeValue = null;
		Object preCustomerValue = null;
		Object preBaseValue = null;

		for (String fieldPath : fieldPaths) {
			Object upgradeValue = upgradeData.get(fieldPath);
			Object customerValue = customerData.get(fieldPath);
			Object baseValue = baseData.get(fieldPath);
			// compare with base data with customer data
			if (!Util.equals(baseValue, customerValue)) {
				// compare the base data with upgrade data
				if (!Util.equals(baseValue, upgradeValue)) {
					result = UpgradeResult.Renamed;
					if (logger.isDebugEnabled()) {
						logConflict(fieldPath, upgradeValue, customerValue,
								baseValue);
					}
					break;
				} else {
					if (result == UpgradeResult.Upgraded) {
						result = UpgradeResult.Renamed;
						if (logger.isDebugEnabled()) {
							logConflict(preFieldPath, preUpgradeValue,
									preCustomerValue, preBaseValue);
							logConflict(fieldPath, upgradeValue, customerValue,
									baseValue);
						}
						break;
					} else {
						result = UpgradeResult.Kept_Customer;
						preFieldPath = fieldPath;
						preUpgradeValue = upgradeValue;
						preCustomerValue = customerValue;
						preBaseValue = baseValue;
					}
				}
			} else {
				if (result == UpgradeResult.Kept_Customer) {
					result = UpgradeResult.Renamed;
					if (logger.isDebugEnabled()) {
						logConflict(preFieldPath, preUpgradeValue,
								preCustomerValue, preBaseValue);
						logConflict(fieldPath, upgradeValue, customerValue,
								baseValue);
					}
					break;
				} else {
					result = UpgradeResult.Upgraded;
					preFieldPath = fieldPath;
					preUpgradeValue = upgradeValue;
					preCustomerValue = customerValue;
					preBaseValue = baseValue;
				}
			}
		}

		return result;
	}

	private List<Object> getValues(List<SMMap> datas, String fieldPath) {
		List<Object> values = new ArrayList<Object>();
		for (Object data : datas) {
			SMMap dataMap = (SMMap) data;
			values.add(dataMap.get(fieldPath));
		}
		return values;
	}

	@Override
	public void upgradeData(String fileName, List<SMMap> upgradeList,
			List<String> fieldPaths, List<String> skippedList,
			KeyValueChangePolicy keyValueChangePolicy,
			DataUpgradeInterceptor interceptor, DataService upgradeDataService,
			String[] keyPaths, String[] joinArrayPaths,
			boolean isBaseQueryable, DataService baseDataService,
			boolean useBaseKey, String[] baseKeyPaths,
			String[] baseJoinArrayPaths) {
		String entityName = Util.radEntityNameToJava(fileName);
		for (SMMap upgradeData : upgradeList) {
			Object[] keyValues = DAMUtil.getValues(upgradeData, keyPaths);

			String keyValueStr = Util.formatKey(keyValues);
			if (!skippedList.isEmpty() && skippedList.contains(keyValueStr)) {
				// skip non-pk values
				continue;
			}

			Object[] customerKeyValues = keyValues;
			if (keyValueChangePolicy != null) {
				// get the changed key
				List<Object> oldKey = keyValueChangePolicy.getOldKey(Arrays
						.asList(keyValues));
				if (oldKey != null) {
					customerKeyValues = oldKey.toArray();
				}
			}

			UpgradeResult result = UpgradeResult.Already_Current;
			List<SMMap> customerDatas = DAMUtil.getDatasByKey(template,
					entityName, keyPaths, customerKeyValues);
			if (customerDatas != null && !customerDatas.isEmpty()) {
				// used for update
				List<SMMap> upgradeDatas = null;

				SMMap customerData = null;
				boolean isUnique = true;
				List<String> conflictPaths = null;
				if (customerDatas.size() == 1) {
					customerData = customerDatas.get(0);
					conflictPaths = compare(fieldPaths, upgradeData,
							customerData);
				} else {
					isUnique = false;
					skippedList.add(keyValueStr);
					upgradeDatas = upgradeDataService.getDatasByKey(entityName,
							keyPaths, keyValues, joinArrayPaths);
					conflictPaths = compare(fieldPaths, upgradeDatas,
							customerDatas);
				}

				if (conflictPaths != null && !conflictPaths.isEmpty()) {
					result = UpgradeResult.Renamed;
					if (isBaseQueryable) {
						Object[] baseKeyValues = customerKeyValues;
						if (useBaseKey) {
							// use the configured key for base
							baseKeyValues = DAMUtil.getValues(upgradeData,
									baseKeyPaths);
						}
						List<SMMap> baseDatas = baseDataService.getDatasByKey(
								entityName, baseKeyPaths, baseKeyValues,
								baseJoinArrayPaths);
						if (baseDatas != null && !baseDatas.isEmpty()) {
							if (isUnique) {
								if (baseDatas.size() == 1) {
									SMMap baseData = baseDatas.get(0);
									result = compare(conflictPaths,
											upgradeData, customerData, baseData);
								} else {
									isUnique = false;
									skippedList.add(keyValueStr);
									upgradeDatas = upgradeDataService
											.getDatasByKey(entityName,
													keyPaths, keyValues,
													joinArrayPaths);
									result = compare(conflictPaths,
											upgradeDatas, customerDatas,
											baseDatas);
								}
							} else {
								result = compare(conflictPaths, upgradeDatas,
										customerDatas, baseDatas);
							}
						}
					}
				}

				// upgrade data
				if (result == UpgradeResult.Upgraded) {
					if (isUnique) {
						interceptor.updateData(entityName, customerData,
								upgradeData);
					} else {
						interceptor.updateDatas(entityName, customerDatas,
								upgradeDatas);
					}
				}
			} else {
				result = UpgradeResult.Added;
				interceptor.addData(entityName, upgradeData);
			}

			interceptor.addResult(fileName, keyValues, result);
		}

		interceptor.flush(entityName);
	}

	@Override
	public void showConflict(String fileName, List<SMMap> upgradeList,
			List<String> fieldPaths, List<String> skippedList,
			KeyValueChangePolicy keyValueChangePolicy,
			DataService upgradeDataService, String[] keyPaths,
			String[] joinedArrayPaths, boolean isBaseQueryable,
			DataService baseDataService, boolean useBaseKey,
			String[] baseKeyPaths, String[] baseJoinedArrayPaths) {
		String entityName = Util.radEntityNameToJava(fileName);
		for (SMMap upgradeData : upgradeList) {
			Object[] keyValues = DAMUtil.getValues(upgradeData, keyPaths);
			String keyValueStr = Util.formatKey(keyValues);
			if (!skippedList.isEmpty() && skippedList.contains(keyValueStr)) {
				// skip non-pk values
				continue;
			}

			Object[] customerKeyValues = keyValues;
			if (keyValueChangePolicy != null) {
				// get the changed key
				List<Object> oldKey = keyValueChangePolicy.getOldKey(Arrays
						.asList(keyValues));
				if (oldKey != null) {
					customerKeyValues = oldKey.toArray();
				}
			}

			List<SMMap> customerDatas = DAMUtil.getDatasByKey(template,
					entityName, keyPaths, customerKeyValues);
			if (customerDatas != null && !customerDatas.isEmpty()) {
				// used for log
				List<SMMap> upgradeDatas = null;
				List<SMMap> baseDatas = null;
				SMMap customerData = null;
				SMMap baseData = null;

				boolean isUnique = true;
				List<String> conflictPaths = null;
				if (customerDatas.size() == 1) {
					customerData = customerDatas.get(0);
					conflictPaths = compare(fieldPaths, upgradeData,
							customerData);
				} else {
					isUnique = false;
					skippedList.add(keyValueStr);
					upgradeDatas = upgradeDataService.getDatasByKey(entityName,
							keyPaths, keyValues, joinedArrayPaths);
					conflictPaths = compare(fieldPaths, upgradeDatas,
							customerDatas);
				}

				if (conflictPaths != null && !conflictPaths.isEmpty()) {
					boolean compareWithBase = isBaseQueryable;
					UpgradeResult result = UpgradeResult.Renamed;
					if (isBaseQueryable) {
						Object[] baseKeyValues = customerKeyValues;
						if (useBaseKey) {
							// use the configured key for base
							baseKeyValues = DAMUtil.getValues(upgradeData,
									baseKeyPaths);
						}
						baseDatas = baseDataService.getDatasByKey(entityName,
								baseKeyPaths, baseKeyValues,
								baseJoinedArrayPaths);
						if (baseDatas != null && !baseDatas.isEmpty()) {
							if (isUnique) {
								if (baseDatas.size() == 1) {
									baseData = baseDatas.get(0);
									result = compare(conflictPaths,
											upgradeData, customerData, baseData);
								} else {
									isUnique = false;
									skippedList.add(keyValueStr);
									upgradeDatas = upgradeDataService
											.getDatasByKey(entityName,
													keyPaths, keyValues,
													joinedArrayPaths);
									result = compare(conflictPaths,
											upgradeDatas, customerDatas,
											baseDatas);
								}
							} else {
								result = compare(conflictPaths, upgradeDatas,
										customerDatas, baseDatas);
							}
						} else {
							compareWithBase = false;
						}
					}

					if (logger.isDebugEnabled()
							&& result == UpgradeResult.Renamed) {
						if (!compareWithBase) {
							String conflictPath = conflictPaths.get(0);
							if (isUnique) {
								Object upgradeValue = upgradeData
										.get(conflictPath);
								Object customerValue = customerData
										.get(conflictPath);
								logConflict(conflictPath, upgradeValue,
										customerValue);
							} else {
								Object upgradeValues = getValues(upgradeDatas,
										conflictPath);
								Object customerValues = getValues(
										customerDatas, conflictPath);
								logConflict(conflictPath, upgradeValues,
										customerValues);
							}
						}

						logger.debug("Find conflict for dbdict:" + fileName
								+ ",key:" + keyValueStr);
					}
				}
			}
		}
	}
}
